/*
 * Copyright 2007. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the GNU Lesser General Public License, Version 2. You
 * can find a copy of the license at:
 * 
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */
package com.sinai.mshab.client.ui.basic;

import java.util.ArrayList;
import java.util.Iterator;

import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.Widget;
import com.sinai.mshab.client.util.Answer;
import com.sinai.mshab.client.services.ServiceAsync;
import com.sinai.mshab.client.ui.aggregate.DataReceiver;
import com.sinai.mshab.client.ui.aggregate.FormData;

/**
 * Very similar to text question componenent, however as you start typing this
 * component will retreive the possible answers from the database stored in this
 * components field name and table name and present you with a drop down list.
 * 
 * @author serge
 * 
 */
public class SuggestTextComponent extends QuestionComponent {

	private int width = 200;

	private boolean expand = true;

	private String lookupDbFieldName = null;

	private String lookupTableName = null;

	private String storeDbFieldName = null;

	private String storeTableName = null;

	protected MultiWordSuggestOracle oracle;

	/**
	 * Creates a new TextQuestionComponent.
	 * 
	 * @param lookupDbFieldName
	 *            the name of the field to which the suggest box information
	 *            corresponds to.
	 * @param lookupTableName
	 *            the name of the database table where the suggest box
	 *            information will be stored.
	 * @param description
	 *            The description label that goes before suggest box (eg. Name)
	 */
	public SuggestTextComponent(String lookupDbFieldName,
			String lookupTableName, String description) {
		initializeWidget(lookupDbFieldName, lookupTableName, description, "",
				"");
	}

	/**
	 * Creates a new TextQuestionComponent.
	 * 
	 * @param lookupDbFieldName
	 *            the name of the field to which the suggest box information
	 *            corresponds to.
	 * @param lookupTableName
	 *            the name of the database table where the suggest box
	 *            information will be stored.
	 * @param description
	 *            The description label that goes before suggest box (eg. Name)
	 * @param quantifier
	 *            The quantifier label that goes after suggest box (eg. /years)
	 */
	public SuggestTextComponent(String lookupDbFieldName,
			String lookupTableName, String description, String quantifier) {
		initializeWidget(lookupDbFieldName, lookupTableName, description,
				quantifier, "");
	}

	/**
	 * Creates a new TextQuestionComponent.
	 * 
	 * @param lookupDbFieldName
	 *            the name of the field to which the suggest box information
	 *            corresponds to.
	 * @param lookupTableName
	 *            the name of the database table where the suggest box
	 *            information will be stored.
	 * @param description
	 *            The description label that goes before suggest box (eg. Name)
	 * @param quantifier
	 *            The quantifier label that goes after suggest box (eg. /years)
	 * @param error_msg
	 *            The error message that gets displayed if the attached
	 *            validators do not pass
	 */
	public SuggestTextComponent(String lookupDbFieldName,
			String lookupTableName, String description, String quantifier,
			String error_msg) {
		initializeWidget(lookupDbFieldName, lookupTableName, description,
				quantifier, error_msg);
	}

	/**
	 * Helper method in creating a new <code>TextComponent</code>. This
	 * allows for us to have one central method for creating a
	 * <code>TextComponent</code> even though we take in a different number of
	 * parameters.
	 * 
	 * @param lookupDbFieldName
	 *            the name of the field to which the suggest box information
	 *            corresponds to.
	 * @param lookupTableName
	 *            the name of the database table where the suggest box
	 *            information will be stored.
	 * @param description
	 *            The description label that goes before suggest box (eg. Name)
	 * @param quantifier
	 *            The quantifier label that goes after suggest box (eg. /years)
	 * @param error_msg
	 *            The error message that gets displayed if the attached
	 *            validators do not pass
	 */
	private void initializeWidget(String lookupDbFieldName,
			String lookupTableName, String description, String quantifier,
			String error_msg) {

		// set up oracle
		oracle = new MultiWordSuggestOracle();

		this.lookupDbFieldName = lookupDbFieldName;
		this.lookupTableName = lookupTableName;

		SuggestBox widget = new SuggestBox(oracle);

		// use BasicFormComponent's initialize.
		super.initialize(this.lookupDbFieldName, this.lookupTableName, description,
				quantifier, error_msg, widget, true);

		// set the limit
		widget.setLimit(10);

		// need a keyboard listener
		widget.addKeyboardListener(new SuggestKeyboardListener());
		widget.addFocusListener(this);

		// set the proper alt
		widget.setTitle(description);
		widget.setWidth(width + "px");
	}

	/**
	 * Set an alternate store location. Since a SuggestTextComponent assumes
	 * that it's contents will be stored in the same table it's looking up, this
	 * method allows you to change that behaviour.
	 * 
	 * @param field
	 *            the database field where the contents of the widget should be
	 *            stored.
	 * @param table
	 *            the database table where the field is located.
	 */
	public void setStorageFields(String field, String table) {
		dbfieldName = field;
		tableName = table;
		storeDbFieldName = field;
		storeTableName = table;
	}

	public void sendAnswer(DataReceiver receiver) {
		Answer answer = getAnswer();
		if (answer != null) {
			receiver.sendData(answer);
		}
	}

	public Answer getAnswer() {

		Answer answer = new Answer();
		answer.add(tableName, dbfieldName, ((SuggestBox) widget).getText());
		answer.add(hiddenFields);
		return answer;
	}

	/**
	 * When to focus is lost validate the field.
	 */
	public void onFocus(Widget sender) {
		String name = ((SuggestBox) widget).getStyleName();
		((SuggestBox) widget).setStyleName(name + "_focus");
	}

	/**
	 * When to focus is lost validate the field.
	 */
	public void onLostFocus(Widget sender) {

		String name = ((SuggestBox) widget).getStyleName();
		if (name.indexOf("_focus") != -1) {
			((SuggestBox) widget).setStyleName(name.substring(0, name.length()
					- "_focus".length()));
		}

		validate();
	}

	/**
	 * Sets the text of the suggest box to 'message'
	 * 
	 * @param message
	 *            The text that will go to the text field.
	 */
	public void setText(String message) {
		((SuggestBox) widget).setText(message);
	}

	/**
	 * Returns the text of the suggest box.
	 * 
	 * @param message
	 *            The text that is in the suggest box
	 */
	public String getText() {
		return ((SuggestBox) widget).getText();
	}

	public Object clone() {
		BasicFormComponent t;
		t = new SuggestTextComponent(lookupDbFieldName, lookupTableName,
				description, quantifier, error_msg);

		// if there is a store dbfield name and table name, we set copy it to
		// the new widget.
		if (storeDbFieldName != null && storeTableName != null) {
			((SuggestTextComponent) t).setStorageFields(storeDbFieldName,
					storeTableName);
		}

		t.setMask(this.mask);
		t.setValidator(this.validator);

		if (subquestion)
			t.setAsSubQuestion();

		return t;
	}

	/**
	 * Populate this widget with FormData using an Answer object
	 * 
	 * @param answer
	 *            the answer object containing the data.
	 */
	public boolean populate(Answer answer) {

		// if answer is null, do nothing
		if (answer == null) {
			return false;
		}

		// walking through all the fields-values of answer
		Iterator field_names = answer.getHashMap().keySet().iterator();
		while (field_names.hasNext()) {
			String field_name = (String) field_names.next();

			// if field name matches our widget's field name, use this value
			if (field_name.equalsIgnoreCase(dbfieldName)) {
				String value = (String) answer.getHashMap().get(field_name);
				((SuggestBox) widget).setText(value);

				// apply mask if it exists
				if (mask != null) {
					setText(mask.applyMask(getText()));
				}

				// if we populaeted the widget with nothing, return false.
				if (value == null || value.trim().length() == 0) {
					return false;
				}

				return true;
			}
		}
		return false;
	}

	/**
	 * Populate this widget with FormData
	 */
	public boolean populate() {
		Answer a = FormData.getData(tableName);
		return populate(a);
	}

	/**
	 * Set's the suggest box's css style.
	 * 
	 * @param style
	 *            the new css style name
	 */
	public void setInnerStyleName(String style) {
		((SuggestBox) widget).setStyleName(style);
	}

	/**
	 * Set's the description's label css style.
	 * 
	 * @param style
	 *            the new css style name
	 */
	public void setDescriptionStyleName(String style) {
		this.description_label.setStyleName(style);
	}

	/**
	 * Upon calling this method, the suggest box will no longer expand in width
	 * to accomodate large text being entered
	 */
	public void disableExpand() {
		expand = false;
	}

	public String getOperator() {
		return "LIKE";
	}

	/**
	 * Sets the width of this suggest box
	 * 
	 * @param w
	 *            the new width in pixels
	 */
	public void setWidth(int w) {
		((SuggestBox) widget).setWidth(w + "px");
	}

	/**
	 * Gets a suggestion from the server side to be used in the oracle to show
	 * all results that match <code>getText()</code> + <code>keyCode</code>.
	 * 
	 * @param sender
	 *            the Widget sending the request, it should be a
	 *            {@link SuggestBox}.
	 * @param keyCode
	 *            the new key entered by the user.
	 */
	protected void getSuggestion(Widget sender, char keyCode) {
		System.out.println((int) keyCode);

		String typedSoFar = getText();

		// append the latest key to the search string.
		if ((int) keyCode != 8) {
			typedSoFar += keyCode;
		}
		// if backspace, remove last letter from keycode if possible
		else if (typedSoFar.length() > 1) {
			typedSoFar = typedSoFar.substring(0, typedSoFar.length() - 1);
		}

		if (typedSoFar.length() > 1) {
			System.out.println("populating the suggest box with length: "
					+ typedSoFar.length());
			ServiceAsync service = FormData.getInstance().getService();
			service.getSuggestions(lookupDbFieldName, lookupTableName,
					typedSoFar, new SuggestionReceived());
		}

		// notify of change
		notifyObservers(this);
	}

	/**
	 * The {@link KeyboardListenerAdapter} to be used with {@link SuggestBox} to
	 * grab suggestions from the server side via RPCs.
	 * 
	 * @author Arthur Kalmenson
	 * 
	 */
	private class SuggestKeyboardListener extends KeyboardListenerAdapter {
		public void onKeyDown(Widget sender, char keyCode, int modifiers) {
			getSuggestion(sender, keyCode);
		}
	}

	/**
	 * The {@link AsyncCallback} called after the server side finishes the RPC
	 * to gather suggestions based on the text the user entered.
	 * 
	 * @author Arthur Kalmenson
	 */
	private class SuggestionReceived implements AsyncCallback {

		public void onFailure(Throwable caught) {
			// TODO Auto-generated method stub

		}

		public void onSuccess(Object result) {
			ArrayList suggestions = (ArrayList) (result);
			oracle.addAll(suggestions);
			((SuggestBox) widget).setFocus(true);
		}
	}
}
